package com.gpj.learn.netty.nio.chat;

import com.alibaba.fastjson.util.IOUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @className ChatClient
 * @description TODO
 * @author GPJ
 * @date 2020/2/17 14:29
 * @version 1.0
 **/
@Slf4j
public class ChatClient {
    public static final long WAIT_TIME_OUT = 2 * 1000;
    private static final int REBUILD_LIMIT_NUM = 10;

    private int selectorBugNum = 1;
    private long selectorBugTime = 0L;

    private SocketChannel channel;
    private Selector selector;

    public ChatClient() {
        try {
            this.channel = SocketChannel.open();
            selector = Selector.open();

            channel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public void start(){
        connection();
        checkEvents();
    }
    
    public void connection(){
        try {
            final boolean connect = this.channel.connect(new InetSocketAddress("127.0.0.1", 9090));
            if(connect){
                this.channel.register(selector, SelectionKey.OP_READ);
                setMessage(this.channel);
            }else{
                this.channel.register(selector, SelectionKey.OP_CONNECT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private void checkEvents(){
        // 循环查询selector中事件是否触发
        this.selectorBugTime = System.nanoTime();
        while (true){
            checkEvent(this::setMessage, this::showMessage);
            selectorBugHandle();
        }
    }

    private void checkEvent(Consumer<SocketChannel> connEventHandler, Consumer<SelectionKey> readEventHandler){
        try {
            this.selector.select(WAIT_TIME_OUT);
            final Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
            if(selectionKeys != null && !selectionKeys.isEmpty()){
                final Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    final SelectionKey selectionKey = iterator.next();
                    if(selectionKey.isConnectable()){
                        connEventHandler.accept((SocketChannel) selectionKey.channel());
                        iterator.remove();
                    }else if (selectionKey.isReadable()){
                        readEventHandler.accept(selectionKey);
                        iterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void setMessage(SocketChannel channel){
        new Thread(() -> {
            try {
                Thread.sleep(2 * 1000);
                String message = "【" + Thread.currentThread().getName() + "】发送消息: 你好，我是聊天机器人" + UUID.randomUUID().toString();
                System.out.println(message);
                SocketUtil.write(channel, message.getBytes());
                Thread.sleep(10 * 1000);
                channel.close();
            } catch (IOException | InterruptedException e) {
                log.error("消息发送失败 --> {}", e);
            }
        }).start();
    }

    private void showMessage(SelectionKey selectionKey){
        try {
            final SelectableChannel channel = selectionKey.channel();
            final byte[] bytes = SocketUtil.read((ReadableByteChannel) channel);
            System.out.println("接收消息:" + new String(bytes, IOUtils.UTF8));
        } catch (IOException e) {
            log.error("消息展示失败 --> {}", e);
        }
    }

    private void selectorBugHandle(){
        if(System.nanoTime() - this.selectorBugTime < selectorBugNum * TimeUnit.MILLISECONDS.toNanos(WAIT_TIME_OUT)){
            log.warn("selector#select方法未阻塞");
            if(this.selectorBugNum ++ >= REBUILD_LIMIT_NUM ){
                rebuildSelector();
                this.selectorBugNum = 0;
            }
        }
    }

    private void rebuildSelector(){
        try {
            final Selector newSelector = Selector.open();
            Selector oldSelector = selector;

            for (SelectionKey key : selector.keys()) {
                key.channel().register(newSelector, key.interestOps());
            }

            selector = newSelector;
            oldSelector.close();
            log.warn("已经重建Selector");
        } catch (IOException e) {
            log.error("重建Selector失败 --> {}", e);
        }

    }



}
